package ar.com.e2solver.solver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import ar.com.e2solver.conf.PuzzleTheme;
import ar.com.e2solver.exception.BusinessException;
import ar.com.e2solver.model.Puzzle;
import ar.com.e2solver.solver.localsearch.MultiThreadHillClimbing;
import ar.com.e2solver.solver.localsearch.SimpleHillClimbing;
import ar.com.e2solver.solver.operator.CrossoverOperator;
import ar.com.e2solver.solver.operator.CrossoverOperatorFactory;
import ar.com.e2solver.solver.operator.MutationOperator;
import ar.com.e2solver.solver.operator.MutationOperatorFactory;

public class GeneticAlgorithmSolver {

	private PuzzleTheme thePuzzleTheme;
	private int populationSize;
	private int iterationNumber;
	private Population population;
	
	public GeneticAlgorithmSolver(PuzzleTheme puzzleTheme, int populationSize, int iterationsNumber){
		this.thePuzzleTheme = puzzleTheme;
		this.populationSize = populationSize;
		this.iterationNumber = iterationsNumber;
		this.population = new Population();
	}
	
	public GAResult solve() throws BusinessException{
		long initialMilisecs = System.currentTimeMillis();
		population.addAll(generatePopulation(getPuzzleTheme(), this.populationSize));
		
		int mutationProbability = 15;
		Individual best = population.getBestIndividual();
		boolean solved = false;
		
		for(int iteration = 0; iteration < getIterationNumber() && !solved; iteration++){
			List<Individual> selectedIndividuals = RouletteWheelSelector.select(population, (int) Math.floor(populationSize * 0.5));
			List<Individual> newIndividuals = evolve(selectedIndividuals, mutationProbability, (int) Math.floor(populationSize * 0.8));
			mergeInANewGeneration(newIndividuals);	
			 
			if(iteration % 2000 == 0){
				//float comparition = best.getPuzzle().compare(population.getBestIndividual().getPuzzle());
				//System.out.println("iteracion: " + iteration + " mejor: " + population.getBestIndividual().getFitnessValue() + " (" + comparition + ")" + " Varianza: " + population.getVS());
	
				best = population.getBestIndividual();
				solved = population.getBestIndividual().getMaxPossibleFitness() == population.getBestIndividual().getFitnessValue();
			}
			
			if(iteration != 0 && iteration % 10000 == 0){
				selectedIndividuals = RouletteWheelSelector.select(population, (int)Math.floor(population.getSize() * 0.15));
				newIndividuals = MultiThreadHillClimbing.getInstance().climb(selectedIndividuals);
				
				mergeInANewGeneration(newIndividuals);
			}
			
		}
		
		long finalMilisecs = System.currentTimeMillis();
		return new GAResult(population, finalMilisecs - initialMilisecs);
	}
	
	private List<Individual> generatePopulation(PuzzleTheme puzzleTheme, int populationSize) {
		List<Individual> initialPopulation = new ArrayList<Individual>();
		for(int i = 0; i < populationSize; i++){
			Puzzle puzzle = new Puzzle(puzzleTheme);
			puzzle.shuffle();
			initialPopulation.add(new Individual(puzzle));
		}
		
		return initialPopulation;
	}
	
	private List<Individual> evolve(List<Individual> selectedIndividuals, int mutationProbability, int size) throws BusinessException {
		List<Individual> newIndividuals = new ArrayList<Individual>();
		
		Individual offspring;
		
		
		for (int i = 0; i < size; i++) {
			int operator = RandomConstants.RANDOM.nextInt(100);
			if(operator < mutationProbability){
				offspring = selectedIndividuals.get(RandomConstants.RANDOM.nextInt(selectedIndividuals.size()));
				offspring = doMutation(offspring);
			} else {
				offspring = doCrossover(selectedIndividuals);
			}
			newIndividuals.add(offspring);
		}
					
		return newIndividuals;
	}
	
	private Individual doCrossover(List<Individual> individuals) throws BusinessException {
		int selected1 = RandomConstants.RANDOM.nextInt(individuals.size());
		int selected2 = RandomConstants.RANDOM.nextInt(individuals.size());
		
		Individual individual1 = individuals.get(selected1);
		Individual individual2 = individuals.get(selected2);
		
		//int operationNumber = RandomConstants.RANDOM.nextInt(10);
		CrossoverOperator operator = CrossoverOperatorFactory.getOperator(0);
		
		return operator.operate(individual1, individual2);
	}

	private Individual doMutation(Individual individual) throws BusinessException {
		int operationNumber = RandomConstants.RANDOM.nextInt(20);
		MutationOperator operator = MutationOperatorFactory.getOperator(operationNumber);
		
		return operator.operate(individual);
	}

	private void mergeInANewGeneration(List<Individual> newIndividuals) {
		List<Individual> indList = new ArrayList<Individual>(population.getIndividuals());
		Collections.sort(indList, new IndividualComparator());
		List<Individual> newPopulationIndividuals = new ArrayList<Individual>();
		
		for(int i = newIndividuals.size(); i < indList.size(); i++){
			newPopulationIndividuals.add(indList.get(i));
		}
				
		newPopulationIndividuals.addAll(newIndividuals);
		population = new Population(newPopulationIndividuals);
	}	

	public PuzzleTheme getPuzzleTheme() {
		return thePuzzleTheme;
	}
	
	public void setThePuzzleTheme(PuzzleTheme thePuzzleTheme) {
		this.thePuzzleTheme = thePuzzleTheme;
	}

	public int getPopulationSize() {
		return populationSize;
	}

	public void setPopulationSize(int populationSize) {
		this.populationSize = populationSize;
	}

	public int getIterationNumber() {
		return iterationNumber;
	}

	public void setIterationNumber(int iterationNumber) {
		this.iterationNumber = iterationNumber;
	}

}
